// TestCaseModel
// (c)2007 Mark Bulmahn. mBulmahn@web.de
// License: GPL 2.0
// Project: TestCaseGenerator. http://www.codeplex.com/TestCaseGenerator

using System;
using System.Data;
using System.Collections.Generic;
using System.Text;

namespace TestCaseModel
{
    /// <summary>
    /// The test case datatable holds the combinations of parameters and their
    /// classes as long as the possible result states of an object. Primarily
    /// this is a data container to persist the results of automatic test case
    /// generation and can also be modified manually with the TestCaseGenerator UI
    /// It also implements the function for finding test results by aplying the
    /// result rules of a test model.
    /// </summary>
    [Serializable]
    public class TestCaseDataTable : DataTable
    {
        public const string COL_TESTCASEID = "TestcaseId";
        public const string COL_RESULTSTATE = "ResultStateCol";

        Parameters param = null;
        public Parameters Param
        {
            get { return param; }
            set { param = value; }
        }

        ExpectedResults results = null;
        public ExpectedResults Results
        {
            get { return results; }
            set { results = value; }
        }

        public TestCaseDataTable()
        {
            TableName = "TestCases";
        }

        public new TestCaseDataTable Copy()
        {
            return (TestCaseDataTable)base.Copy();
        }

        /// <summary>
        /// Apply the defined rules to the existing test cases
        /// </summary>
        /// <param name="resultRules">The rules that are defined for the model</param>
        public void ProcessRules(ResultRules resultRules)
        {
            // first set the result state for all rows to false
            // this makes it possible to find the test cases that are not
            // affected by any of the rules (needed for model checking
            foreach(DataRow row in this.Rows)
                row[COL_RESULTSTATE] = false;

            // process each rule
            foreach (ResultRule rule in resultRules)
            {
                // get the rows which are hit by the condition in the rule
                DataRow[] affectedRows = Select(rule.RuleExpression);

                // and now set the state of each affected test case, ... or delte it ...
                foreach (DataRow dr in affectedRows)
                {
                    // since the row is affected by at least one result rule
                    // it is a valid test cases and should not be marked as suspicious
                    // in model checking later on
                    dr[COL_RESULTSTATE] = true;

                    if (!rule.ObsoleteTestCase)
                    {
                        dr[rule.ResultName] = rule.ResultState;
                    }
                    else
                    {
                        // ... because it's obsolete.
                        Rows.Remove(dr);
                    }
                }
            }
        }


        /// <summary>
        /// Build a test case talbe from given combinations
        /// </summary>
        /// <param name="combinations">An array of combinations of Parameters and equvialence classes
        /// caluclated from the model</param>
        public void BuildTestCaseTable(string[,] combinations)
        {
            // Prepare the table by creating the structure
            CreateStructure();

            // now fill in the values from the raw array data
            int tcId = 1;
            for (int line = 0; line < combinations.GetLength(0) - 1; line++)
            {
                // create a new row
                int e = 0;
                DataRow newRow = this.NewRow();
                newRow[COL_TESTCASEID] = tcId;

                // fill the row from the raw combination array
                foreach (Parameter par in param.Values)
                {
                    newRow[par.Name] = combinations[line + 1, e];
                    e++;
                }
                Rows.Add(newRow);
                tcId++;
            }
        }

        /// <summary>
        /// Helper function that creates the columns for the test case table
        /// </summary>
        private void CreateStructure()
        {
            // add the id column
            DataColumn idCol = new DataColumn(COL_TESTCASEID, typeof(int));
            idCol.Unique = true;
            idCol.ExtendedProperties.Add(TestModel.COLTYPE, TestModel.COLTYPE_ID);
            Columns.Add(idCol);

            // add a column for each parameter
            foreach (Parameter par in param.Values)
            {
                DataColumn newDc = new DataColumn(par.Name, typeof(string));
                newDc.ExtendedProperties.Add(TestModel.COLTYPE, TestModel.COLTYPE_PARAMETER);
                Columns.Add(newDc);
            }

            // add the special column for storing result state
            DataColumn idResState = new DataColumn(COL_RESULTSTATE, typeof(bool));
            idResState.ExtendedProperties.Add(TestModel.COLTYPE, TestModel.COLTYPE_RESULTSTATE);
            Columns.Add(idResState);
            
        }


        /// <summary>
        /// Helper function that tries to merge already existing columns
        /// </summary>
        public void ResyncResultColumns()
        {
            // add new columns if they are missing
            foreach (ExpectedResult expRes in results)
            {
                if (ResultColumnIsMissing(expRes.Id))
                {
                    DataColumn newCol = new DataColumn(expRes.Id,
                        typeof(bool));
                    newCol.ExtendedProperties.Add(TestModel.COLTYPE, TestModel.COLTYPE_RESULT);
                    Columns.Add(newCol);
                }
            }

            // remove columns that are missing in the expected result list
            List<string> columnsToBeRemoved = new List<string>();
            foreach (DataColumn dc in Columns)
            {
                if ((string)dc.ExtendedProperties[TestModel.COLTYPE] == TestModel.COLTYPE_RESULT)
                {
                    if (!results.Exists(dc.ColumnName))
                        columnsToBeRemoved.Add(dc.ColumnName);
                }
            }
            foreach (string s in columnsToBeRemoved)
                Columns.Remove(s);

        }

        /// <summary>
        /// Checks whethere a certaing result column is missing in the table
        /// </summary>
        /// <param name="colName">The name of the column</param>
        /// <returns>True if the column is missing</returns>
        private bool ResultColumnIsMissing(string colName)
        {
            // todo: Auf DataTable verlagern
            bool result = true;
            foreach (DataColumn dc in Columns)
            {
                if ((dc.ColumnName == colName) && ((string)dc.ExtendedProperties[TestModel.COLTYPE] == TestModel.COLTYPE_RESULT))
                {
                    result = false;
                    break;
                }
            }
            return result;
        }


        /// <summary>
        /// Get a list of all result column names currently in the data table
        /// </summary>
        /// <returns>Stringlist of the result columns names</returns>
        public List<string> GetResultColumns()
        {
            List<string> cols = new List<string>();
            foreach (DataColumn dc in Columns)
            {
                if ((string)dc.ExtendedProperties[TestModel.COLTYPE] == TestModel.COLTYPE_RESULT)
                {
                    cols.Add(dc.ColumnName);
                }
            }
            return cols;
        }

        /// <summary>
        /// Get a list of all parameter column names currently in the data table
        /// </summary>
        /// <returns>Stringlist of the parameter columns names</returns>
        public List<string> GetParamColumns()
        {
            List<string> cols = new List<string>();
            foreach (DataColumn dc in Columns)
            {
                if ((string)dc.ExtendedProperties[TestModel.COLTYPE] == TestModel.COLTYPE_PARAMETER)
                {
                    cols.Add(dc.ColumnName);
                }
            }
            return cols;
        }

        /// <summary>
        /// Locate a specific row in the data by building a key of all parameter columns
        /// </summary>
        /// <param name="key">Key of all parameter columns</param>
        /// <returns>List of matching data rows</returns>
        public DataRow LocateRowByKey(string key)
        {
            List<string> paramCols = GetParamColumns();
            foreach (DataRow dr in Rows)
            {
                string k = "";
                foreach (string col in paramCols)
                {
                    k += dr[col];
                }
                if (key == k)
                {
                    return dr;
                }
            }
            return null;
        }

        /// <summary>
        /// Exports the current testcase data to humand readable HTML
        /// </summary>
        /// <param name="fn">Filename for export file</param>
        public void ExportAsHtml(string fn)
        {
            List<string> htmlOut = new List<string>();
            htmlOut.Add("<html>");
            htmlOut.Add("<body>");
            htmlOut.Add("<table border=\"1\">");

            htmlOut.Add("<dr>");
            foreach (DataColumn dc in this.Columns)
            {
                htmlOut.Add("<td>" + dc.ColumnName + "</td>");
            }
            htmlOut.Add("</dr>");

            foreach (DataRow dr in this.Rows)
            {
                htmlOut.Add("<tr>");
                foreach (DataColumn dc in this.Columns)
                {
                    htmlOut.Add("<td>" + dr[dc.ColumnName].ToString() + "</td>");
                }
                htmlOut.Add("</tr>");
            }

            htmlOut.Add("</table>");
            htmlOut.Add("</body>");
            htmlOut.Add("</html>");

            System.IO.File.WriteAllLines(fn, htmlOut.ToArray());
        }

        /// <summary>
        /// Process all null conditions on parameters
        /// </summary>
        public void ProcessNullConditions(Parameters pars)
        {
            foreach (Parameter par in pars.Values)
            {
                if (par.NullConditionExpr != null)
                {
                    try
                    {
                        DataRow[] rowsAffected = this.Select(par.NullConditionExpr.RuleExpression);
                        foreach (DataRow r in rowsAffected)
                        {
                            r[par.Name] = "---";
                        }
                    }
                    catch (Exception ex)
                    {
                        string msg = "Error processing null-condition for Parameter '" + par.Name + "'\nDetails:" + ex.Message + ".";
                        throw new Exception(msg);
                    }
                }
            }
            RemoveDuplicates();
            Renumber();
        }


        /// <summary>
        /// remove all duplicate test cases (e.g. after processing
        /// the parameters' null conditions)
        /// </summary>
        private void RemoveDuplicates()
        {
            // a list of keys for finding duplicates
            List<string> keys = new List<string>();

            // get all parameter columns to build the unique key
            List<string> parCols = GetParamColumns();

            // set up a list for the cases that will be deleted
            List<int> casesToDelete = new List<int>();

            // now go through all test cases to find duplicates
            foreach (DataRow dr in Rows)
            {
                // build unique key from all parameters
                string key = "";
                foreach (string col in parCols)
                    key += dr[col];

                // check if this key already was found
                if (!keys.Contains(key))
                {
                    keys.Add(key);
                }
                else
                {
                    // now we found a duplicate and have to remember the id for deletion
                    casesToDelete.Add((int) dr[COL_TESTCASEID]);
                }
            }

            // so we can do the removal
            foreach (int id in casesToDelete)
            {
                DataRow[] rowsToDelete = Select("[" + COL_TESTCASEID + "]=" + id.ToString());
                foreach (DataRow dr in rowsToDelete)
                    Rows.Remove(dr);                
            }
        }

        /// <summary>
        /// Renumber the test cases after deletion of unwanted test cases
        /// (e.g. from result conditions or after removal from null conditions)
        /// </summary>
        private void Renumber()
        {
            int i = 1;
            foreach (DataRow dr in Rows)
            {
                dr[COL_TESTCASEID] = i++;
            }
        }

        public void ReplaceWithExampleData()
        {
            foreach(Parameter par in param.Values)
            {
                foreach (DataRow row in this.Rows)
                {
                    string curr = row[par.Name].ToString();
                    if (curr != "---")
                    {
                        string newVal = par.Classes.GetExampleData(curr);
                        row[par.Name] = newVal;
                    }
                }
            }
        }
    }
}
